package rt2

import (
	"errors"
	"sync"

	"gitee.com/zhang-li17/rt2/pcol"

	"github.com/google/uuid"
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/types/known/timestamppb"
)

func SendProtoMsg(c *MultiCastClient, msgType pcol.MessageType, msg proto.Message) error {
	bs, err := proto.Marshal(msg)
	if err != nil {
		return err
	}
	data := c.Dec.PackAndEncode(FrameType(msgType), bs)

	_, err = c.Conn.Write(data)
	return err
}

func SendProtoMsgWithSeq(c *MultiCastClient, msgType pcol.MessageType, msg proto.Message, seq int32) error {
	bs, err := proto.Marshal(msg)
	if err != nil {
		return err
	}
	data := c.Dec.PackAndEncodeWithSeq(FrameType(msgType), bs, seq)

	_, err = c.Conn.Write(data)
	return err
}

func RequestResOnce(c *MultiCastClient, resNames []string) error {
	return RequestRes(c, resNames, 0, 0)
}

func RequestRes(c *MultiCastClient, resNames []string, interval int32, times int32) error {
	req := &pcol.ResValRequest{
		Names:         resNames,
		Interval:      interval,
		MaxReplyTimes: times,
	}
	return SendProtoMsg(c, pcol.MessageType_RES_VAL_REQUEST, req)
}

func CancelRequestRes(c *MultiCastClient, groupId int32) error {
	req := &pcol.ResValRequest{
		GroupId: groupId,
		Disable: true,
	}
	return SendProtoMsg(c, pcol.MessageType_RES_VAL_REQUEST, req)
}

type TypeResGenerator func(resType pcol.ResourceType, v any) (*pcol.Res, error)

var resGenerators map[pcol.ResourceType]TypeResGenerator
var genLock sync.Mutex
var (
	errResTypeAlreadyRegistered = errors.New("resource type already registered")
)

func init() {
	resGenerators = make(map[pcol.ResourceType]TypeResGenerator)
	registerResType(pcol.ResourceType_BOOL, func(resType pcol.ResourceType, v any) (*pcol.Res, error) {
		if val, ok := v.(bool); ok {
			return &pcol.Res{
				ResType: resType,
				BoolVal: val,
			}, nil
		}
		return nil, errors.New("not bool")
	})
	registerResType(pcol.ResourceType_INT, func(resType pcol.ResourceType, v any) (*pcol.Res, error) {
		if val, ok := v.(int32); ok {
			return &pcol.Res{
				ResType: resType,
				IntVal:  val,
			}, nil
		}
		return nil, errors.New("not int")
	})
	registerResType(pcol.ResourceType_DOUBLE, func(resType pcol.ResourceType, v any) (*pcol.Res, error) {
		if val, ok := v.(float64); ok {
			return &pcol.Res{
				ResType:   resType,
				DoubleVal: val,
			}, nil
		}
		return nil, errors.New("not double")
	})
	registerResType(pcol.ResourceType_STRING, func(resType pcol.ResourceType, v any) (*pcol.Res, error) {
		if val, ok := v.(string); ok {
			return &pcol.Res{
				ResType:   resType,
				StringVal: val,
			}, nil
		}
		return nil, errors.New("not string")
	})
	registerResType(pcol.ResourceType_BOOL_ARRAY, func(resType pcol.ResourceType, v any) (*pcol.Res, error) {
		if val, ok := v.([]bool); ok {
			return &pcol.Res{
				ResType:      resType,
				BoolArrayVal: &pcol.BoolArray{Val: val},
			}, nil
		}
		return nil, errors.New("not bool array")
	})
	registerResType(pcol.ResourceType_INT_ARRAY, func(resType pcol.ResourceType, v any) (*pcol.Res, error) {
		if val, ok := v.([]int32); ok {
			return &pcol.Res{
				ResType:     resType,
				IntArrayVal: &pcol.IntArray{Val: val},
			}, nil
		}
		return nil, errors.New("not int array")
	})
	registerResType(pcol.ResourceType_DOUBLE_ARRAY, func(resType pcol.ResourceType, v any) (*pcol.Res, error) {
		if val, ok := v.([]float64); ok {
			return &pcol.Res{
				ResType:        resType,
				DoubleArrayVal: &pcol.DoubleArray{Val: val},
			}, nil
		}
		return nil, errors.New("not double array")
	})
	registerResType(pcol.ResourceType_STRING_ARRAY, func(resType pcol.ResourceType, v any) (*pcol.Res, error) {
		if val, ok := v.([]string); ok {
			return &pcol.Res{
				ResType:        resType,
				StringArrayVal: &pcol.StringArray{Val: val},
			}, nil
		}
		return nil, errors.New("not string array")
	})
	registerResType(pcol.ResourceType_BINARY, func(resType pcol.ResourceType, v any) (*pcol.Res, error) {
		if val, ok := v.([]byte); ok {
			return &pcol.Res{
				ResType:   resType,
				BinaryVal: val,
			}, nil
		}
		return nil, errors.New("not string array")
	})
}
func registerResType(resType pcol.ResourceType, gen TypeResGenerator) error {
	genLock.Lock()
	defer genLock.Unlock()
	if _, ok := resGenerators[resType]; !ok {
		resGenerators[resType] = gen
		return nil
	}
	return errResTypeAlreadyRegistered
}
func SetRes(c *MultiCastClient, name string, resType pcol.ResourceType, val any) error {
	if gen, ok := resGenerators[resType]; ok {
		res, err := gen(resType, val)
		if err != nil {
			return err
		}
		res.Name = name
		req := &pcol.ResValSetRequest{
			Res: []*pcol.Res{res},
		}
		return SendProtoMsg(c, pcol.MessageType_RES_VAL_SET_REQUEST, req)
	}
	return errors.New("resource type not registered")
}

// SetMultiRes
// args: name1, resType1, val1, name2, resType2, val2...
func SetMultiRes(c *MultiCastClient, args ...any) error {
	if len(args)%3 != 0 {
		return errors.New("invalid arg numbers")
	}
	req := &pcol.ResValSetRequest{
		Res: make([]*pcol.Res, len(args)/3),
	}
	for i := 0; i < len(args); i += 3 {
		var name string
		var resType pcol.ResourceType
		if t, ok := args[i+1].(pcol.ResourceType); ok {
			resType = t
		} else {
			return errors.New("invalid resource type")
		}
		if n, ok := args[i].(string); ok {
			name = n
		} else {
			return errors.New("invalid resource name")
		}
		if gen, ok := resGenerators[resType]; ok {
			res, err := gen(resType, args[i])
			if err != nil {
				return err
			}
			res.Name = name
			req.Res[i/3] = res
		} else {
			return errors.New("resource type not registered")
		}
	}
	return SendProtoMsg(c, pcol.MessageType_RES_VAL_SET_REQUEST, req)
}

func SendEvent(c *MultiCastClient, evtType pcol.EventType, evtLevel pcol.EventLevel, description string, data []byte) error {
	evt := &pcol.Event{
		Id:          uuid.New().String(),
		Type:        evtType,
		Level:       evtLevel,
		Description: description,
		Data:        data,
		Timestamp:   timestamppb.Now(),
	}
	return SendProtoMsg(c, pcol.MessageType_EVENT, evt)
}

func SendDebugEvent(c *MultiCastClient, evtType pcol.EventType, description string, data []byte) error {
	return SendEvent(c, evtType, pcol.EventLevel_DEBUG, description, data)
}
func SendInfoEvent(c *MultiCastClient, evtType pcol.EventType, description string, data []byte) error {
	return SendEvent(c, evtType, pcol.EventLevel_INFO, description, data)
}
func SendWarnEvent(c *MultiCastClient, evtType pcol.EventType, description string, data []byte) error {
	return SendEvent(c, evtType, pcol.EventLevel_WARNING, description, data)
}

func SendErrorEvent(c *MultiCastClient, evtType pcol.EventType, description string, data []byte) error {
	return SendEvent(c, evtType, pcol.EventLevel_ERROR, description, data)
}
